package research_aggregator;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Semaphore;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

public class Service extends Thread {
	private SSLConnection sslClient;
	private boolean isWork = true; 
	private DataBase database;
	private boolean isAuthorized = false;
	private Cache cache;
	private Semaphore sem_cache;
	private Semaphore sem_read;
	private String identity;
	private CapabilityList capabilityList;
	private AuditLog log = new AuditLog();

	public Service(Cache cache) {

		this.cache = cache;
	}

	public Service(SSLConnection ssl, Semaphore sem_cache, Semaphore sem_read, Cache cache) {

		this.database = new DataBase("seokje.iptime.org:3306", "ra database", "hie", "hie");
		this.sem_cache = sem_cache;
		this.sem_read = sem_read;
		this.cache = cache;
		this.sslClient = ssl;
		//		this.cachedHealthRecord = new Cache(sem_cache);

	}

	public void run() {
		Message message = null;
		System.out.println("thread created");
		try {

			while (isWork) {
				message = new Message(sslClient.readLine());
				System.out.println("message = " + message.toXml());
				if (message.getMessage().equals("logIn")) {
					logIn(message);
				}
				else if (message.getMessage().equals("newAccount")) {
					newAccount(message);
				}
				else if (message.getMessage().equals("requestPermission") && isAuthorized) {
					sslClient.writeLine(new Message().setMessage("accepted").toXml());
					requestPermission(message);
				}
				else if (message.getMessage().equals("requestRecords") && isAuthorized) {
					sslClient.writeLine(new Message().setMessage("accepted").toXml());
					reqeustRecords(message);
				}
				else if (message.getMessage().equals("logoff") && isAuthorized) {
					break;
				}
			}

		} catch (Exception e) {
			isWork = false;
		}

		try {
			sslClient.close();
		} catch (Exception e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		System.out.println("thread closed");
	}

	private void requestPermission(Message message) throws IOException {
		String id = message.getIdentityAttribute("id");
		String cl = message.getIdentityAttribute("xml");
		try {
			database.connect();
			database.connection.setAutoCommit(false);
			database.preparedStatement = database.connection.prepareStatement("update accounts set cl=? where id=?");
			database.preparedStatement.setString(1, cl);
			database.preparedStatement.setString(2, id);
			database.preparedStatement.executeUpdate();
			database.connection.commit();
			
			database.preparedStatement.close();
			database.connection.close();
			sslClient.writeLine(new Message().setMessage("updated").toXml());
			capabilityList = new CapabilityList(cl);
		}
		catch (SQLException e) {
			sslClient.writeLine(new Message().setMessage("failed").toXml());
			e.printStackTrace();
		}

	}

	private void newAccount(Message message) throws SQLException {
		String id = message.getIdentityAttribute("id");
		String password = message.getIdentityAttribute("password");
		String salt = message.getIdentityAttribute("salt");		

		System.out.println("newACC");
		try {
			database.connect();
			database.connection.setAutoCommit(false);
			database.preparedStatement = database.connection.prepareStatement("insert into accounts(id, password, salt) values (?, ?, ?)");
			database.preparedStatement.setString(1, id);
			database.preparedStatement.setString(2, password);
			database.preparedStatement.setString(3, salt);
			database.preparedStatement.executeUpdate();
			database.connection.commit();	
			System.out.println("newAccount for " + id + " created");
			sslClient.writeLine(new Message().setMessage("created").toXml());

			isAuthorized = true;
			this.identity = id;
			log.writeLog(identity, "created new account");

		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			try {
				sslClient.writeLine(new Message().setMessage("failed").toXml());
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			isWork = false;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			database.preparedStatement.close();
			database.connection.close();
		}
	}



	private void logIn(Message message) throws SQLException, IOException {
		String id = message.getIdentityAttribute("id");
		String password = message.getIdentityAttribute("password");
		System.out.println("login pahse 1");
		try {
			database.connect();
			database.statement = database.connection.createStatement();
			database.resultSet = database.statement.executeQuery("SELECT * FROM accounts WHERE id='" + id + "'");
			database.resultSet.next();
			String storedPassword = database.resultSet.getString("password");
			String salt = database.resultSet.getString("salt");
			String xml = database.resultSet.getString("cl");

			System.out.println("login phase2");
			byte[] bSalt = string2Bytes(salt);

			String hashedPassword = bytes2String(getHash(password, bSalt));

			System.out.println("login pahse3");
			if (hashedPassword.equals(storedPassword)) {
				System.out.println("granted");
				capabilityList = new CapabilityList(xml);
				sslClient.writeLine(new Message().setMessage("authorized").setIdentity(null, null, null, xml).toXml());
			}
			else {
				System.out.println("denied");
				sslClient.writeLine(new Message().setMessage("denied").toXml());
				isWork = false;
			}			
		} catch (SQLException e) {
			sslClient.writeLine(new Message().setMessage("failed").toXml());
			isWork = false;
		} catch (IOException e) {
			isWork = false;
		} finally {
			database.statement.close();
			database.connection.close();
			isAuthorized = true;
		}
		System.out.println("login phase 4");
	}
	
	private void reqeustRecords(Message message) throws InterruptedException, IOException {
		getFilteredList(capabilityList);

	}

	public void getFilteredList(CapabilityList cl) throws InterruptedException, IOException {
		//sem_read.acquire();
		System.out.println("getFilteredList");
		Set<String> set = cache.records.keySet();

		Iterator<String> itr = set.iterator();
		String str;
		SAXBuilder builder = new SAXBuilder();

		Double weightLow, weightUpper;
		Double heightLow, heightUpper;
		int ageLow, ageUpper;
		String genderCap = null, raceCap = null, cityCap = null, stateCap = null;
		String medicalCap = null;	    

		Document Clist = cl.CList;
		Element clroot = Clist.getRootElement();
		if (clroot.getChild("weight") == null) {
			weightLow = -1.0;
			weightUpper = -1.0;
		}
		else {
			weightLow = Double.valueOf(clroot.getChild("weight").getAttributeValue("low"));
			weightUpper = Double.valueOf(clroot.getChild("weight").getAttributeValue("upper"));
		}

		if (clroot.getChild("height") == null) {
			heightLow = -1.0;
			heightUpper = -1.0;
		}
		else {
			heightLow = Double.valueOf(clroot.getChild("height").getAttributeValue("low"));
			heightUpper = Double.valueOf(clroot.getChild("height").getAttributeValue("upper"));
		}

		if (clroot.getChild("age") == null) {
			ageLow = -1;
			ageUpper = -1;
		}
		else {
			ageLow = Integer.valueOf(clroot.getChild("age").getAttributeValue("low"));
			ageUpper = Integer.valueOf(clroot.getChild("age").getAttributeValue("upper"));
		}

		if (clroot.getChild("gender") != null) genderCap = clroot.getChildText("gender");

		if (clroot.getChild("race") != null) raceCap = clroot.getChildText("race");

		if (clroot.getChild("city") != null) cityCap = clroot.getChildText("city");

		if (clroot.getChild("state") != null) stateCap = clroot.getChildText("state");

		if (clroot.getChild("medical") != null) medicalCap = "all"; 

		XMLOutputter outputter = new XMLOutputter(Format.getRawFormat());
		while (itr.hasNext()) {
			str = itr.next();
			DeIdentifiedHealthRecord hr = cache.records.get(str);
			System.out.println("orig = " + outputter.outputString(hr.publicRecord));
			try {   		
				Document doc = builder.build(new ByteArrayInputStream("<public></public>".getBytes()));
				Element record = doc.getRootElement();
				Element publicStat = new Element("publicstat");
				boolean isEmpty = true;
				boolean isThis = true;
				if (weightLow >= 0) {
					Double weight = hr.publicRecordObject.publicStat.weight;
					if (weightLow <= weight && weight <= weightUpper) {
						publicStat.addContent(new Element("weight").setText(Double.toString(weight)));
						isEmpty = false;
					}					
					else isThis = false;
				}
				if (heightLow >= 0) {
					Double height = hr.publicRecordObject.publicStat.height;
					if (heightLow <= height && height <= heightUpper) {
						publicStat.addContent(new Element("height").setText(Double.toString(height)));
						isEmpty = false;
					}
					else isThis = false;
				}
				if (ageLow >= 0) {
					int age = hr.publicRecordObject.publicStat.age;
					if (ageLow <= age && age <= ageUpper) {
						publicStat.addContent(new Element("age").setText(Integer.toString(age)));
						isEmpty = false;
					}
					else isThis = false;
				}
				if (genderCap != null) {
					String gender = hr.publicRecordObject.publicStat.gender;
					if (gender.equals(genderCap) || genderCap.equals("all")) {
						publicStat.addContent(new Element("gender").setText(gender));
						isEmpty = false;
					}					
					else isThis = false;
				}
				if (cityCap != null) {
					String city = hr.publicRecordObject.publicStat.city;
					if (city.equals(cityCap) || cityCap.equals("all")) {
						publicStat.addContent(new Element("city").setText(city));
						isEmpty = false;
					}					
					else isThis = false;
				}
				if (stateCap != null) {
					String state = hr.publicRecordObject.publicStat.state;
					if (state.equals(stateCap) || stateCap.equals("all")) {
						publicStat.addContent(new Element("state").setText(state));
						isEmpty = false;
					}					
					else isThis = false;
				}
				if (medicalCap != null) {
					Element medical = hr.publicRecord.getRootElement().getChild("medical");
					record.addContent((Element) medical.clone());
					isEmpty = false;
				}
				else isThis = false;

				if (!isEmpty && isThis) {
					record.addContent(publicStat);
					sslClient.writeLine(new Message().setMessage("found").setHealthRecord(null, null, outputter.outputString(record).replaceAll("[\n\r]", ""), null, null, null).toXml());
					System.out.println("sent = " + outputter.outputString(record).replaceAll("[\n\r]", ""));
				}
			} catch (JDOMException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		sslClient.writeLine(new Message().setMessage("end").toXml());
		// sem_read.release();
	}


	private byte[] getHash(String string, byte[] salt) {	
		MessageDigest mda;
		try {
			mda = MessageDigest.getInstance("SHA-512");
			mda.reset();
			mda.update(salt);
			byte[] digest = mda.digest(string.getBytes());
			for (int i = 0; i < 1000; i++) {
				mda.reset();
				digest = mda.digest(digest);
			}

			return digest;

		} catch (NoSuchAlgorithmException e) {
			return null;
		} 
	}

	/*
	 * http://stackoverflow.com/questions/521101/using-sha1-and-rsa-with-java-security-signature-vs-messagedigest-and-cipher
	 */
	private String bytes2String(byte[] bytes) {
		StringBuilder string = new StringBuilder();
		for (byte b: bytes) {
			String hexString = Integer.toHexString(0x00FF & b);
			string.append(hexString.length() == 1 ? "0" + hexString : hexString);
		}
		return string.toString();
	}

	/*
	 * http://stackoverflow.com/questions/140131/convert-a-string-representation-of-a-hex-dump-to-a-byte-array-using-java
	 */
	public byte[] string2Bytes(String s) {
		int len = s.length();
		byte[] data = new byte[len / 2];
		for (int i = 0; i < len; i += 2) {
			data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
					+ Character.digit(s.charAt(i+1), 16));
		}
		return data;
	}
}
